package ns3

import (
	"blockchains-aio/ethrpc"
	"blockchains-aio/util"
	"bufio"
	"context"
	"fmt"
	"log"
	"math/rand"
	"os"
	"path"
	"strings"
	"sync"
	"time"
)

var (
	once   sync.Once
	worker *Worker
)

type Source int

const (
	Ns3Space = iota
	Ns3PhyRxTrace
	Ns3PhyTxTrace
	Ns3MacRxTrace
	Ns3MacTxTrace
	Ns3Log
)

func GetWorker() *Worker {
	once.Do(func() {
		worker = new(Worker)
		worker.IsRebuiding = false

		worker.Ns3StatsPath = os.Getenv("NS3_STATS_PATH")
		//worker.Ns3StatsPath = "/home/francis/ns3"
		if len(worker.Ns3StatsPath) != 0 {
			loc := new(DataFilesLoc)
			loc.build(worker.Ns3StatsPath)
			worker.dataFilesLoc = loc
		}

		worker.logLines = make(chan SourcedLine, 2000)

		worker.spaceLines = make(chan SourcedLine, 500)
		worker.phyRxLines = make(chan SourcedLine, 2000)
		worker.phyTxLines = make(chan SourcedLine, 2000)
		worker.macRxLines = make(chan SourcedLine, 2000)
		worker.macTxLines = make(chan SourcedLine, 2000)

		worker.stats = new(Stats)
		//worker.stats.NodesAccumulativeTrafficView = enrichNodePhyMacRxTxArray(worker.stats.NodesAccumulativeTrafficView, 510)
		//worker.stats.FlushViews(2)
		//

	})

	return worker
}

type SourcedLine struct {
	Source Source
	Raw    string
}

type DataFilesLoc struct {
	SpaceFile      string
	PhyRxTraceFile string
	PhyTxTraceFile string
	MacRxTraceFile string
	MacTxTraceFile string

	Range    string
	Topology string

	LogFile string
}

func (loc *DataFilesLoc) build(dataFilesFolder string) {
	loc.SpaceFile = path.Join(dataFilesFolder, "space.txt")
	loc.PhyRxTraceFile = path.Join(dataFilesFolder, "PhyRxTrace.txt")
	loc.PhyTxTraceFile = path.Join(dataFilesFolder, "PhyTxTrace.txt")
	loc.MacRxTraceFile = path.Join(dataFilesFolder, "MacRxTrace.txt")
	loc.MacTxTraceFile = path.Join(dataFilesFolder, "MacTxTrace.txt")

	loc.Range = path.Join(dataFilesFolder, "range.txt") // either  range or topology
	loc.Topology = path.Join(dataFilesFolder, "Topology.txt")

	//loc.LogFile = "/data/blockchainsAioProj/out.log"
	loc.LogFile = path.Join(dataFilesFolder, "ns3.console.log")
}

type Worker struct {
	Ns3StatsPath string

	ctx          context.Context
	dataFilesLoc *DataFilesLoc

	logLines   chan SourcedLine
	spaceLines chan SourcedLine
	phyRxLines chan SourcedLine
	phyTxLines chan SourcedLine
	macRxLines chan SourcedLine
	macTxLines chan SourcedLine

	stats        *Stats
	stopRoutines func()

	IsRebuiding bool
}

func (w *Worker) RemoveDataFiles() error {
	err := os.Remove(w.dataFilesLoc.PhyRxTraceFile)
	if err != nil {
		return err
	}

	err = os.Remove(w.dataFilesLoc.MacTxTraceFile)
	if err != nil {
		return err
	}

	err = os.Remove(w.dataFilesLoc.MacRxTraceFile)
	if err != nil {
		return err
	}

	err = os.Remove(w.dataFilesLoc.PhyTxTraceFile)
	return err

}

func (w *Worker) IsReady() bool {
	//if w.Ns3StatsPath == "" {
	//	return false
	//}
	//
	//return true
	if util.IsPathExists(w.dataFilesLoc.PhyRxTraceFile) &&
		util.IsPathExists(w.dataFilesLoc.MacTxTraceFile) &&
		util.IsPathExists(w.dataFilesLoc.MacRxTraceFile) &&
		util.IsPathExists(w.dataFilesLoc.LogFile) &&
		//util.IsPathExists(w.dataFilesLoc.SpaceFile) &&
		util.IsPathExists(w.dataFilesLoc.PhyTxTraceFile) {
		return true
	}

	return false
}

// collect lines with discrete channels
func (w *Worker) StartFollowersConcurrently(ctx context.Context) {
	cancelCtx, cancelFunc := context.WithCancel(ctx)
	w.stopRoutines = cancelFunc

	// spawn log follower
	go Follow(cancelCtx, w.dataFilesLoc.LogFile, Ns3Log, w.logLines)

	// spawn space follower
	go Follow(cancelCtx, w.dataFilesLoc.SpaceFile, Ns3Space, w.spaceLines)

	//spawn PhyRx,PhyTx,MacRx,MacTx followers
	go Follow(cancelCtx, w.dataFilesLoc.PhyRxTraceFile, Ns3PhyRxTrace, w.phyRxLines)
	go Follow(cancelCtx, w.dataFilesLoc.PhyTxTraceFile, Ns3PhyTxTrace, w.phyTxLines)
	go Follow(cancelCtx, w.dataFilesLoc.MacRxTraceFile, Ns3MacRxTrace, w.macRxLines)
	go Follow(cancelCtx, w.dataFilesLoc.MacTxTraceFile, Ns3MacTxTrace, w.macTxLines)

}

func (w *Worker) Collect(seconds int, ecw *ethrpc.EthClientWrapper) *Stats {
	w.stats.FlushPartViews(seconds)
	//timer := time.NewTimer(time.Duration(seconds) * time.Second)
	timeout := time.After(time.Duration(seconds) * time.Second)

	for {
		select {
		case <-timeout:
			//worker.stats.BlockView = getRandBlock()
			if ecw != nil {
				w.stats.BlockView = getEthBlock(ecw)
			}
			//worker.stats.SpacePeriodView = getSpaces(510)

			w.stats.RangeView = w.getAllNodesRange() // present nodes' range 2025/02/25
			//worker.stats.TopologyView = w.GetAllNodesTopology()
			//worker.stats.RangeView = w.getNNodesRange(30) // present nodes' range 2025/02/25
			//worker.stats.TopologyView = w.getNNodesTopology(30)

			spvp := w.stats.SpacePeriodViewPointer
			w.stats.SpacePeriodView = *spvp
			//log.Println("----------", len(w.stats.SpacePeriodView))

			return w.stats
		case logLine := <-w.logLines:
			worker.stats.appendLog(logLine.Raw)
		case spaceLine := <-w.spaceLines:
			//log.Printf("space raw: %s", spaceLine.Raw)
			raw, err := parseSpace(spaceLine.Raw)
			if err != nil {
				log.Printf("%s is a wrong space.", spaceLine.Raw)
				continue
			}

			worker.stats.appendSpace(raw.newSpace())
		case line := <-w.phyRxLines:
			log.Println("phyRxLine:", line.Raw)
			tr, err := parseTrace(line.Raw)
			if err != nil {
				log.Printf("%s is a wrong phyRx.", line.Raw)
				continue
			}
			node, iat := tr.newIAT()
			worker.stats.AddPhyRx(node, iat)
		case line := <-w.phyTxLines:
			log.Println("phyTxLine:", line.Raw)
			tr, err := parseTrace(line.Raw)
			if err != nil {
				log.Printf("%s is a wrong phyTx.", line.Raw)
				continue
			}
			node, iat := tr.newIAT()
			worker.stats.AddPhyTx(node, iat)
		case line := <-w.macRxLines:
			log.Println("macRxLine:", line.Raw)
			tr, err := parseTrace(line.Raw)
			if err != nil {
				log.Printf("%s is a wrong macRx.", line.Raw)
				continue
			}
			node, iat := tr.newIAT()
			worker.stats.AddMacRx(node, iat)
		case line := <-w.macTxLines:
			log.Println("macTxLine:", line.Raw)
			tr, err := parseTrace(line.Raw)
			if err != nil {
				log.Printf("%s is a wrong macTx.", line.Raw)
				continue
			}
			node, iat := tr.newIAT()
			worker.stats.AddMacTx(node, iat)
		}
	}

	return worker.stats
}

func (w *Worker) Stop() {
	w.stopRoutines()
}

func (w *Worker) Flush() {
	w.stats.FlushViews(2)
	//w.stats.NodesAccumulativeTrafficView = enrichNodePhyMacRxTxArray(worker.stats.NodesAccumulativeTrafficView, 510)
}

func getEthBlock(ecw *ethrpc.EthClientWrapper) Blockchain {
	ctx := context.Background()
	block, err := ecw.GetLatestBlock(ctx)
	if err != nil {
		return Blockchain{}
	}
	pendingTxCount, err := ecw.PendingTransactionCount(ctx)
	if err != nil {
		return Blockchain{}
	}

	return Blockchain{
		Throughput: block.Transactions().Len(),
		Success:    int(pendingTxCount),
	}
}

func getRandBlock() Blockchain {
	throughput := rand.Intn(14)
	success := rand.Intn(11) + 90

	return Blockchain{
		Throughput: throughput,
		Success:    success,
	}
}

func getSpaces(len int) []Space {
	spaces := make([]Space, len)
	for i := 0; i < len; i++ {
		node := fmt.Sprintf("节点 %v", i)

		spaces[i] = Space{
			T:    float64(i),
			Node: node,
			X:    float64(rand.Intn(1000)),
			Y:    float64(rand.Intn(1000)),
		}
	}

	return spaces
}

func (w *Worker) GetAllNodesTopology() []NodeTopology {
	topology := make([]NodeTopology, 0)

	if !util.IsPathExists(w.dataFilesLoc.Topology) {
		return topology
	}

	fileHandle, err := os.OpenFile(w.dataFilesLoc.Topology, os.O_RDONLY, 0666)
	if err != nil {
		log.Printf("openning file error: %v", err)
		return topology
	}
	defer fileHandle.Close()

	scanner := bufio.NewScanner(fileHandle)

	for scanner.Scan() {
		lineTxt := strings.TrimSpace(scanner.Text())
		if len(lineTxt) == 0 {
			continue
		}
		top, err := pareseTopology(lineTxt)
		if err != nil {
			log.Printf("parsing line error: %v", err)
			continue
		}
		topology = append(topology, *top)
	}

	if err := scanner.Err(); err != nil {
		log.Printf("scanning file error: %v", err)
	}

	return topology

}

func (w *Worker) getNNodesTopology(n int) []NodeTopology {
	topology := make([]NodeTopology, 0)

	if !util.IsPathExists(w.dataFilesLoc.Topology) {
		return topology
	}

	fileHandle, err := os.OpenFile(w.dataFilesLoc.Topology, os.O_RDONLY, 0666)
	if err != nil {
		log.Printf("openning file error: %v", err)
		return topology
	}
	defer fileHandle.Close()

	scanner := bufio.NewScanner(fileHandle)
	number := 0

	for scanner.Scan() {
		if n > 0 && number > n {
			break
		}

		lineTxt := strings.TrimSpace(scanner.Text())
		if len(lineTxt) == 0 {
			continue
		}
		top, err := pareseTopology(lineTxt)
		if err != nil {
			log.Printf("parsing line error: %v", err)
			continue
		}
		topology = append(topology, *top)
		number++
	}

	if err := scanner.Err(); err != nil {
		log.Printf("scanning file error: %v", err)
	}

	return topology

}

func (w *Worker) getAllNodesRange() []NodeRange {
	nodesRange := make([]NodeRange, 0)

	if !util.IsPathExists(w.dataFilesLoc.Range) {
		return nodesRange
	}

	fileHandle, err := os.OpenFile(w.dataFilesLoc.Range, os.O_RDONLY, 0666)
	if err != nil {
		log.Printf("openning file error: %v", err)
		return nodesRange
	}
	defer fileHandle.Close()

	scanner := bufio.NewScanner(fileHandle)

	for scanner.Scan() {
		lineTxt := strings.TrimSpace(scanner.Text())
		if len(lineTxt) == 0 {
			continue
		}
		nr, err := pareseRange(lineTxt)
		if err != nil {
			log.Printf("parsing line error: %v", err)
			continue
		}
		nodesRange = append(nodesRange, *nr)
	}

	if err := scanner.Err(); err != nil {
		log.Printf("scanning file error: %v", err)

	}

	return nodesRange

}

func (w *Worker) getNNodesRange(n int) []NodeRange {
	nodesRange := make([]NodeRange, 0)

	if !util.IsPathExists(w.dataFilesLoc.Range) {
		return nodesRange
	}

	fileHandle, err := os.OpenFile(w.dataFilesLoc.Range, os.O_RDONLY, 0666)
	if err != nil {
		log.Printf("openning file error: %v", err)
		return nodesRange
	}
	defer fileHandle.Close()

	scanner := bufio.NewScanner(fileHandle)
	number := 0

	for scanner.Scan() {
		if n > 0 && number > n {
			break
		}
		lineTxt := strings.TrimSpace(scanner.Text())
		if len(lineTxt) == 0 {
			continue
		}
		nr, err := pareseRange(lineTxt)
		if err != nil {
			log.Printf("parsing line error: %v", err)
			continue
		}
		nodesRange = append(nodesRange, *nr)
		number++
	}

	if err := scanner.Err(); err != nil {
		log.Printf("scanning file error: %v", err)

	}

	return nodesRange

}

func enrichNodePhyMacRxTxArray(nodes []NodePhyMacRxTx, size int) []NodePhyMacRxTx {
	finalNodes := nodes
	for i := 0; i < size; i++ {
		nn := fmt.Sprintf("节点 %v", i)
		_, found := findNode(nodes, nn)
		if found {
			continue
		}

		finalNodes = append(finalNodes, NodePhyMacRxTx{
			Node: nn,
		})
	}

	return finalNodes
}
